package com.zwcl.common.web.filter;

import com.zwcl.common.core.exception.BaseException;
import com.zwcl.common.core.utils.JsonUtils;
import com.zwcl.common.core.utils.MD5Utils;
import com.zwcl.common.core.utils.ObjectMapUtil;
import com.zwcl.common.core.utils.SpringUtils;
import com.zwcl.common.web.config.OutAccessConfig;
import com.zwcl.common.web.domain.SignRequest;
import com.zwcl.common.web.sign.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Longer
 * @description 获取请求参数并处理。签名校验，报文解密，参数过滤。
 * @date 2020/8/23
 */
@Slf4j
@Configuration
public class OutRequestFilter extends OncePerRequestFilter {
//public class OutRequestFilter implements Filter {

    @Autowired
    private OutAccessConfig outAccessConfig;
    /**
     * 过滤器中要抛出异常，要采用该方案
     */
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Autowired
    @Qualifier("multipartResolver")
    private MultipartResolver multipartResolver;

    /**
     * TODO:在这里可以做接口的重复调用，用requestId，放入redis判断
     * TODO：注意，这个过滤器加上后，会导致mutilFile类型的参数被拦截掉，不再往下传递
     * @param request
     * @param httpServletResponse
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        try {
            //修复mutilFile 上传受到过滤器影响的bug
            String contentType = request.getContentType();
            if (contentType != null && contentType.contains("multipart/form-data")) {
                MultipartHttpServletRequest multipartRequest = multipartResolver.resolveMultipart(request);
                //在这里可以通过multipartRequest 获取参数了
                // 把multipartRequest让请求继续执行，之后的所有拦截器和controller都能继续get参数
                filterChain.doFilter(multipartRequest , httpServletResponse);
                return;
            }
            String requestURL = request.getRequestURI().replace("//","/");
            log.info("请求路径：" + requestURL);
            //校验，获取参数字符串
            RequestWrapper requestWrapper = new RequestWrapper(request);
            if(null==outAccessConfig.getOutUrls() ||  !outAccessConfig.getOutUrls().contains(requestURL)){
                filterChain.doFilter(requestWrapper, httpServletResponse);
                return;
            }
            String bodyString= requestValidte(request,requestWrapper);
            SignRequest signRequest = JsonUtils.jsonToPojo(bodyString, SignRequest.class);
            //step0 参数合法性校验（非空判断等）
            parameterValidate(signRequest);
            //step1 判断请求合法性。1.不允许重复请求（通过请求唯一id判断）2.不允许请求时间与当前时间差距过大（正负10分钟）
            long currentTime = System.currentTimeMillis();
            long subTime = currentTime - signRequest.getTimestamp();
            long tenMinuteMs = 5 * 60 * 1000;       //接口调用时延不能超过5分钟
            if (subTime < -tenMinuteMs || subTime > tenMinuteMs) {
                throw new RuntimeException("请求异常，请求时间异常");
            }
            //step2 签名校验，参数过滤
            checkSign(signRequest);
            //step3 解密报文，传递到下面的过滤链
            if (!StringUtils.isEmpty(signRequest.getRequestData())) {
                // 解密请求报文
                String body = "";
                try {
                    body = new String(Base64.getDecoder().decode(signRequest.getRequestData()), StandardCharsets.UTF_8);
                } catch (Exception e) {
                    log.error("请求参数解密异常：",e);
                    throw new RuntimeException("请求参数解密异常");
                }
                //报文传递至controller层
                requestWrapper.setBodyString(body.getBytes(Charset.forName("UTF-8")));
            }
            //将request传递下去
            filterChain.doFilter(requestWrapper, httpServletResponse);
        }catch (Exception ex){
            resolver.resolveException(request, httpServletResponse, null, new BaseException(ex.getMessage()));
            return;
        }
    }



    public Boolean checkSign(SignRequest signRequest){
        Map<String, Object> paramMap = ObjectMapUtil.bean2Map(signRequest);
        String reqSign= paramMap.get("sign").toString();
        paramMap.entrySet().removeIf(entry->entry.getValue()==null);
        String appKey=outAccessConfig.getClients().get(paramMap.get("appId"));
        if(StringUtils.isBlank(appKey)){
            throw new RuntimeException("错误的应用id");
        }
        StringBuilder data=getData(paramMap).append("?key=").append(appKey);
        String verifySign= MD5Utils.md5Hex(data.toString());
        //签名验证
        if (!verifySign.equals(reqSign)) {
            throw new RuntimeException("签名验证失败");
        }
        return true;
    }

    public static StringBuilder getData(Map<String, Object> map) {
        map.remove("sign");
        List<Map.Entry<String, Object>> infoIds = new ArrayList<>(map.entrySet());
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序（字典序）
        Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return (o1.getKey()).compareTo(o2.getKey());
            }
        });
        // 构造签名键值对的格式
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> item : infoIds) {
            if (StringUtils.isNoneBlank(item.getKey())) {
                String key = item.getKey();
                Object val = item.getValue();
                if (ObjectUtils.isNotEmpty(val)) {
                    sb.append(key + "=" + val + "&");
                }
            }
        }
        return sb;
    }

    public String requestValidte(HttpServletRequest request,RequestWrapper requestWrapper ) throws IOException {
        String method = request.getMethod();
        if (!"POST".equals(method)) {
            throw new RuntimeException("暂不支持" + method + "请求方式");
        }
        String bodyString = requestWrapper.getBodyString();
        if (StringUtils.isEmpty(bodyString)) {
            throw new RuntimeException("请求体不能为空");
        }
        log.info("请求参数：" + bodyString);
        return bodyString;
    }

    public void parameterValidate(SignRequest jsonRequest) {
        if (StringUtils.isEmpty(jsonRequest.getAppId())) {
            throw new RuntimeException("参数异常，appId不能为空");
        }
//        if (StringUtils.isEmpty(jsonRequest.getAesKey())) {
//            throw new RuntimeException("参数异常，aseKey不能为空");
//        }
//        if (StringUtils.isEmpty(jsonRequest.getRequestId())) {
//            throw new RuntimeException("参数异常，requestId不能为空");
//        }
        if (StringUtils.isEmpty(jsonRequest.getSign())) {
            throw new RuntimeException("参数异常，sign不能为空");
        }
        if (jsonRequest.getTimestamp() == 0l) {
            throw new RuntimeException("参数异常，timestamp不能为空");
        }
    }

}